/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package hyperbeam;
import hyperbeam.Estructuras.*;
import hyperbeam.Figuras.*;
import hyperbeam.Graficos.*;
import hyperbeam.Lector.*;
import java.util.Hashtable;
import java.util.LinkedList;
/***
 * @author alberto
 */
public class Main {

    /**
     * @param args the command line arguments
     */

    final static double T_DEFAULT = 10000000000.0;
    final static Color backcolor  = new Color(0,0,0);
    final static double invalid = -1000000;
    final static double IA      = 1.0;

    public static void main(String[] args) {
        String filename = "Config.xml";
        Dimension dms = new Dimension();
        Punto3D Camara = new Punto3D();
        LinkedList<Luz> luces = new LinkedList<Luz>();
        LinkedList<Figura> figuras = new LinkedList<Figura>();
        Hashtable<String,Material> materiales = new Hashtable<String,Material>(30);
        Lector.cargarConfiguracionEscena(filename, dms, Camara, luces, figuras, materiales);
        Color[][]buffer = new Color[dms.Ancho][dms.Alto];
        RayTrace(buffer,dms, Camara, Lector.getFilename(filename), figuras, materiales,luces);
        System.out.println("Fin");
    }

    /**
     * Ray tracing
     * @param buffer
     * @param dms
     */
    public static void RayTrace(Color[][]buffer, Dimension dms, Punto3D camara, String filename,
                                LinkedList<Figura> figuras, Hashtable<String,Material> materiales,LinkedList<Luz> luces ){
        double Xd, Yd, Zd;
        double medio = 0.5;
        double[] cambiosX = {0,0,1,1};
        double[] cambiosY = {0,1,0,1};
        Color[]  muestras   = new Color[cambiosX.length];
        Vector direccion = new Vector();
        InicializarBuffer(buffer, dms.Ancho, dms.Alto);
        for (int anchura = 0; anchura < dms.Ancho; anchura++){
            for (int altura = 0; altura < dms.Alto; altura++){
                for (int numMuestra = 0; numMuestra < muestras.length; numMuestra++){
                    Xd = (dms.xMin + (((dms.xMax - dms.xMin) * (anchura + cambiosX[numMuestra])) / dms.Ancho)) - camara.getX();
                    Yd = (dms.yMin + (((dms.yMax - dms.yMin) * (altura  + cambiosY[numMuestra]))  / dms.Alto))  - camara.getY();
                    Zd = (0.0 - camara.getZ());
                    direccion.setXYZ(Xd, Yd, Zd);
                    muestras[numMuestra] = DeQueColor(camara,direccion, figuras, luces, materiales, backcolor);
                }

                  buffer[anchura][altura] = PromediarColores(muestras);
            }
        }
        try{
            EscritorImagenes.inicializarImagen(dms.Ancho, dms.Alto);
            EscritorImagenes.setImage(buffer);
            EscritorImagenes.salvarJPG(filename);
        }
        catch(Exception ex){}
    }

    /**
     * Promedia el color obtenido
     * @param muestras
     * @return
     */
    public static Color PromediarColores(Color[] muestras){
        int red = 0, blue = 0, green = 0;
        for (int numMuestra = 0; numMuestra < muestras.length; numMuestra++){
            red  += muestras[numMuestra].getRed();
            green+= muestras[numMuestra].getGreen();
            blue += muestras[numMuestra].getBlue();
        }
            red   /= muestras.length;
            blue  /= muestras.length;
            green /= muestras.length;
        return new Color (red, green,blue);
    }

    /**
     * Se inicializa el buffer
     * @param buffer
     * @param ancho
     * @param alto
     */
    public static void InicializarBuffer(Color[][] buffer ,int ancho, int alto){
        buffer = new Color[ancho][alto];
        for (int indexFil = 0; indexFil < ancho; indexFil++){
            for (int indexCol = 0; indexCol < alto; indexCol++){
                buffer[indexFil][indexCol] = new Color();
            }
        }
    }

    /**
     * Retorna el color de la primera interseccion
     * @param camara
     * @param dir
     * @param figuras
     * @param materiales
     * @return
     */
    public static Color DeQueColor(Punto3D camara, Vector dir,LinkedList<Figura> figuras, LinkedList<Luz> luces, Hashtable<String, Material> materiales, Color backcolor){
        Color col = new Color();
        Punto3D interseccion = new Punto3D();
        Figura objetoInterceptado = PrimeraInterseccion(camara, dir,interseccion, figuras), obstaculo = null;
        if (objetoInterceptado == null){
            col = backcolor;
        }
        else{
            /*double I = 0.0, P = 0.0, cosN_L, cosV_R, cosN_D, Fatt;
            Vector Luz, Normal, V, R, D;
            col = materiales.get(objetoInterceptado.getIdMaterial()).getColor();
            Normal = objetoInterceptado.obtenerNormal(interseccion);

            //Se escoje la normal
            cosN_D = Vector.ProductoPunto(Normal, dir);
            if (cosN_D > 0){
                Normal.Escalar(-1);
            }
            //Se le da vuelta al ojo
            V = Vector.VectorEscalado(dir, -1);
            V.Normalizar();
            Punto3D posicionLuz;
            Punto3D pivote = new Punto3D();
            Material materialObjeto = materiales.get(objetoInterceptado.idMaterial);
            for (int indexLuces = 0; indexLuces < luces.size(); indexLuces++){
                posicionLuz = luces.get(indexLuces).getPosicion();
                Luz = new Vector(posicionLuz.getX()-interseccion.getX(),posicionLuz.getY()-interseccion.getY(),posicionLuz.getZ()-interseccion.getZ());
                Fatt = 1 / (luces.get(indexLuces).getC1() + luces.get(indexLuces).getC2() * Luz.getNorma()+ luces.get(indexLuces).getC3() * Math.pow(Luz.getNorma(), 2));

                //Sombra
                obstaculo = Sombra(interseccion, Luz, pivote,figuras,objetoInterceptado,Luz.getNorma());
                    if (obstaculo == null){
                        cosN_L = Vector.ProductoPunto(Normal, Luz);
                    if (cosN_L > 1.0)
                        cosN_L = 1.0;
                    if (cosN_L < 0.0)
                        cosN_L = 0.0;

                    I += cosN_L * materialObjeto.getCoeficienteEspecular() * luces.get(indexLuces).getIntensidad() * Fatt;
                    D = Vector.VectorEscalado(Normal, 2 * cosN_L);
                    D.Normalizar();
                    R = Vector.RestarVectores(D, Luz);
                    R.Normalizar();


                    cosV_R = Vector.ProductoPunto(V,R);
                    if (cosV_R > 1.0)
                        cosV_R = 1.0;
                    if (cosV_R < 0.0)
                        cosV_R = 0.0;
                    P += Math.pow(cosV_R, materialObjeto.getKn()) * materialObjeto.getCoeficienteEspecular() * luces.get(indexLuces).getIntensidad();
                }
            }

            I += IA * materialObjeto.getCoeficienteAbsorcionSo();
            if (I > 1.0)
		I = 1.0;
            if (P > 1.0)
		P = 1.0;

            col.setRed((int)  I * materialObjeto.getColor().getRed());
            col.setRed((int) (col.getRed() + P * (1 - col.getRed())));

            col.setGreen((int)  I * materialObjeto.getColor().getGreen());
            col.setGreen((int) (col.getGreen() + P * (1 - col.getGreen())));

            col.setBlue((int)  I * materialObjeto.getColor().getBlue());
            col.setBlue((int) (col.getBlue() + P * (1 - col.getBlue())));*/
            col = materiales.get(objetoInterceptado.getIdMaterial()).getColor();
        }
        return col;
    }


    /**
     *
     * Obtiene la primera figura cvon la que hay intersección
     * @param camara
     * @param Rayo
     * @param interseccion
     * @param figuras
     * @return
     */
    public static Figura PrimeraInterseccion(Punto3D camara, Vector Rayo, Punto3D interseccion, LinkedList<Figura> figuras){
        double t, tmin = T_DEFAULT;
        Figura actual = null;
        interseccion = new Punto3D();
        for (int indexFiguras = 0; indexFiguras < figuras.size(); indexFiguras++){
            figuras.get(indexFiguras).tMin = T_DEFAULT;
            figuras.get(indexFiguras).obtenerInterseccion(camara, Rayo, interseccion);
                    
            if (figuras.get(indexFiguras).hayInterseccion())
            {
                if (figuras.get(indexFiguras).getMinimaDistancia() < tmin && figuras.get(indexFiguras).getMinimaDistancia() != invalid)
                {
                    tmin = figuras.get(indexFiguras).getMinimaDistancia();
                    actual = figuras.get(indexFiguras);
                }
            }            
        }
        if (tmin == T_DEFAULT){
            actual = null;
        } 
        return actual;
    }

    /**
     * Sombra
     * @param interseccion
     * @param Rayo
     * @param pivote
     * @param figuras
     * @param figura
     * @param distanciaLuz
     * @return
     */
    public static Figura Sombra(Punto3D interseccion, Vector Rayo, Punto3D pivote, LinkedList<Figura> figuras, Figura figura, double distanciaLuz){
        double t, tmin = T_DEFAULT, distanciaActual;
        Figura actual = null;
        if (figuras.size() == 1){
            actual = null;
        }
        else{
            for (int indexFiguras = 0; indexFiguras < figuras.size(); indexFiguras++){
                figuras.get(indexFiguras).tMin = T_DEFAULT;
                if (figuras.get(indexFiguras).equals(figura)){
                    continue;
                }

                figuras.get(indexFiguras).obtenerInterseccion(interseccion, Rayo, pivote);
                distanciaActual = figuras.get(indexFiguras).getMinimaDistancia();
                if (distanciaActual < tmin && distanciaActual != invalid && distanciaActual < distanciaLuz )
                {
                    tmin = figuras.get(indexFiguras).getMinimaDistancia();
                    actual = figuras.get(indexFiguras);
                }
            }
            if (tmin == T_DEFAULT){
                actual = null;
            }
        }
        return actual;
    }
}
